 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.ide;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Iterator ;
 import java.util.List ;

 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceChangeEvent;
 import org.eclipse.core.resources.IResourceChangeListener;
 import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.ui.IWorkingSet;
 import org.eclipse.ui.IWorkingSetUpdater;

 /**
  * A working set updater that updates resource working sets on resource deltas
  *
  * @since 3.2
  */
 public class ResourceWorkingSetUpdater implements IWorkingSetUpdater,
         IResourceChangeListener {

     /**
      * Utility class used to help process incoming resource deltas.
      */
     private static class WorkingSetDelta {
         private IWorkingSet fWorkingSet;

         private List fElements;

         private boolean fChanged;

         /**
          * Create a new instance of this class.
          *
          * @param workingSet
          * the working set to track.
          */
         public WorkingSetDelta(IWorkingSet workingSet) {
             fWorkingSet = workingSet;
             fElements = new ArrayList (Arrays.asList(workingSet.getElements()));
         }

         /**
          * Returns the index of this element in the list of known elements.
          *
          * @param element
          * the element to search for
          * @return the index, or -1 if unknown.
          */
         public int indexOf(Object element) {
             return fElements.indexOf(element);
         }

         /**
          * Add a new element to the list of known elements.
          *
          * @param index
          * the index at which to place the element
          * @param element
          * the element to set
          */
         public void set(int index, Object element) {
             fElements.set(index, element);
             fChanged = true;
         }

         /**
          * Remove an element from the list of known elements.
          *
          * @param index
          * the index of the element to remove
          */
         public void remove(int index) {
             if (fElements.remove(index) != null) {
                 fChanged = true;
             }
         }

         /**
          * Process the changes to this delta and update the working set if
          * necessary.
          */
         public void process() {
             if (fChanged) {
                 fWorkingSet.setElements((IAdaptable[]) fElements
                         .toArray(new IAdaptable[fElements.size()]));
             }
         }
     }

     private List fWorkingSets;

     /**
      * Create a new instance of this updater.
      */
     public ResourceWorkingSetUpdater() {
         fWorkingSets = new ArrayList ();
         ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
                 IResourceChangeEvent.POST_CHANGE);
     }

     public void add(IWorkingSet workingSet) {
         checkElementExistence(workingSet);
         synchronized (fWorkingSets) {
             fWorkingSets.add(workingSet);
         }
     }

     public boolean remove(IWorkingSet workingSet) {
         boolean result;
         synchronized (fWorkingSets) {
             result = fWorkingSets.remove(workingSet);
         }

         return result;
     }

     public boolean contains(IWorkingSet workingSet) {
         synchronized (fWorkingSets) {
             return fWorkingSets.contains(workingSet);
         }
     }

     public void dispose() {
         synchronized (fWorkingSets) {
             fWorkingSets.clear();
         }
         ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
     }

     public void resourceChanged(IResourceChangeEvent event) {
         IResourceDelta delta = event.getDelta();
         if (delta == null) {
             return;
         }
         IWorkingSet[] workingSets;
         synchronized (fWorkingSets) {
             workingSets = (IWorkingSet[]) fWorkingSets
                     .toArray(new IWorkingSet[fWorkingSets.size()]);
         }
         for (int w = 0; w < workingSets.length; w++) {
             WorkingSetDelta workingSetDelta = new WorkingSetDelta(
                     workingSets[w]);
             processResourceDelta(workingSetDelta, delta);
             workingSetDelta.process();
         }
     }

     private void processResourceDelta(WorkingSetDelta result,
             IResourceDelta delta) {
         IResource resource = delta.getResource();
         int type = resource.getType();
         int index = result.indexOf(resource);
         int kind = delta.getKind();
         int flags = delta.getFlags();
         if (kind == IResourceDelta.CHANGED && type == IResource.PROJECT
                 && index != -1) {
             if ((flags & IResourceDelta.OPEN) != 0) {
                 result.set(index, resource);
             }
         }
         if (index != -1 && kind == IResourceDelta.REMOVED) {
             if ((flags & IResourceDelta.MOVED_TO) != 0) {
                 result.set(index, ResourcesPlugin.getWorkspace().getRoot()
                         .findMember(delta.getMovedToPath()));
             } else {
                 result.remove(index);
             }
         }

         // Don't dive into closed or opened projects
 if (projectGotClosedOrOpened(resource, kind, flags)) {
             return;
         }

         IResourceDelta[] children = delta.getAffectedChildren();
         for (int i = 0; i < children.length; i++) {
             processResourceDelta(result, children[i]);
         }
     }

     private boolean projectGotClosedOrOpened(IResource resource, int kind,
             int flags) {
         return resource.getType() == IResource.PROJECT
                 && kind == IResourceDelta.CHANGED
                 && (flags & IResourceDelta.OPEN) != 0;
     }

     private void checkElementExistence(IWorkingSet workingSet) {
         List elements = new ArrayList (Arrays.asList(workingSet.getElements()));
         boolean changed = false;
         for (Iterator iter = elements.iterator(); iter.hasNext();) {
             IAdaptable element = (IAdaptable) iter.next();
             boolean remove = false;
             if (element instanceof IResource) {
                 IResource resource = (IResource) element;
                 IProject project = resource.getProject();
                 remove = (project != null ? project.isOpen() : true)
                         && !resource.exists();
             }
             if (remove) {
                 iter.remove();
                 changed = true;
             }
         }
         if (changed) {
             workingSet.setElements((IAdaptable[]) elements
                     .toArray(new IAdaptable[elements.size()]));
         }
     }
 }

